//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include <bulverde.h>
#include <drvpub.h>
#include <memdefs.h>
#include "dmac.h"
#include <INTCBits.h>
#include "audiocard.h"
#include "i2s.h"

EXTERN_C void SSP_Initial(void);

#define NEXT_BUFFER(idx) ((idx == pCxt->nBufCnt - 1) ? 0 : (idx + 1))


#if (_TEST_TYPE == 41)      /* Test for Runtime of ISR */
    #define      SAMPLETIMES  50
    static ULONG ulDmaChOutIsrTime[SAMPLETIMES];
    static ULONG ulDmaChRcvIsrTime[SAMPLETIMES];
    static INT nDmaChOutIsrCounter = 0;
    static INT nDmaChRcvIsrCounter = 0;
#endif //_TEST_TYPE == 41


// There are some bugs for recording when using automatically reset event.
static DzEvent s_syncAudioIn(TRUE, UNSIGNALED);
static DzEvent s_syncAudioOut(TRUE, UNSIGNALED);

#ifdef DEBUG_AUDIO
void DumpDmacRegs(int channel)
{
    kprintf("Dump DMA registers for channel %d\n",channel);

    kprintf("DINT       = %x \n", DINT);
    kprintf("DCSR       = %x\n", DCSR(channel));
    kprintf("DDADR      = %x\n", DDADR(channel));
    kprintf("DSADR      = %x\n", DSADR(channel));
    kprintf("DTADR      = %x\n", DTADR(channel));
    kprintf("DCMD       = %x\n", DCMD(channel));
}

void DumpAc97LinkRegs()
{
    kprintf("Dump AC97Link registers\n");

    kprintf("GCR        =%x\n", GCR);
    kprintf("GSR        =%x\n", GSR);
    kprintf("POCR       =%x\n", POCR);
    kprintf("POSR       =%x\n", POSR);
    kprintf("PCMICR     =%x\n", PCMICR);
    kprintf("PCMISR     =%x\n", PCMISR);
    kprintf("CAR        =%x\n", CAR);
}


void DumpAC97Regs(void)
{
    BYTE i;
    UINT16 Data;

    kprintf("*********************************************\n");
    kprintf("DumpAC97Regs below:\n");

    for (i=0; i<0x7e; i+=2)
    {
        Ac97_Read(i, &Data);
        kprintf("reg 0x%x is 0x%x\n", i, Data);
        if ((i %4) == 0)
            kprintf("\n");
    }
    kprintf("*********************************************\n");
}
#endif

// In These copy functions, parameter cnt means count of bytes
void memcpy_m16_s16(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pSrc16 = (UINT16 *)pSrc;
    UINT16 *pDes16 = (UINT16 *)pDes;

    while (i < cnt) {
        *pDes16 = *pSrc16++;
        *(pDes16 + 1) = *pDes16;
        pDes16 += 2;
        i += 2;
    }
}

void memcpy_m8_s16(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pDes16 = (UINT16 *)pDes;

    while (i < cnt) {
        *pDes16 = BITS_8_TO_16(*pSrc++);
        *(pDes16 + 1) = *pDes16;
        pDes16 += 2;
        i++;
    }
}

void memcpy_s8_s16(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pDes16 = (UINT16 *)pDes;

    while (i < cnt) {
        *pDes16++ = BITS_8_TO_16(*pSrc++);
        *pDes16++ = BITS_8_TO_16(*pSrc++);
        i += 2;
    }
}

void memcpy_s16_s16(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    memcpy(pDes, pSrc, cnt);
}

void memcpy_s16_m8(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pSrc16 = (UINT16 *)pSrc;
    BYTE SrcLeft;
//    BYTE SrcRight;

    // To get clear wave data, use left channel
    while (i < cnt) {
        SrcLeft = BITS_16_TO_8(*pSrc16++);
//        SrcRight = BITS_16_TO_8(*pSrc16++);
        *pSrc16++;
//        *pDes = (SrcLeft >> 1) + (SrcRight >> 1);
        *pDes = SrcLeft;
        pDes++;
        i++;
    }
}

void memcpy_s16_m16(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pSrc16 = (UINT16 *)pSrc;
    UINT16 *pDes16 = (UINT16 *)pDes;
    UINT16 SrcLeft;
//    UINT16 SrcRight;

    // To get clear wave data, use left channel
    while (i < cnt) {
        SrcLeft = *pSrc16++;
//        SrcRight = *pSrc16++;
        pSrc16++;
//        *pDes16 = (SrcLeft >> 1) + (SrcRight >> 1);
        *pDes16 = SrcLeft;
        pDes16++;
        i += 2;
    }
}

void memcpy_s16_s8(BYTE *pDes, BYTE *pSrc, UINT cnt)
{
    UINT i = 0;
    UINT16 *pSrc16 = (UINT16 *)pSrc;

    while (i < cnt) {
        *pDes++ = BITS_16_TO_8(*pSrc16++);
        *pDes++ = BITS_16_TO_8(*pSrc16++);
        i += 2;
    }
}

// Fill the DMA descriptors with appropriate data
// dir == WAVEDIR_PLAYING: Playing
// dir == WAVEDIR_RECORDING: Recording
void FillDescriptors(AudioContext *pCxt, WAVEDIR dir, UINT idx, UINT len)
{
    union DmaCmdReg CmdBuff;
    CmdBuff.DcmdDword = 0; // Set all reserved bits to be 0

    assert(len <= AUDIO_BUFSIZE);
    len &= 0x1FE0; // 32 bytes align, max value for DMA hardware is 8K - 32 bytes

    // set values with bit fields
    CmdBuff.DcmdReg.len = len;  //length of the memory buffer
    CmdBuff.DcmdReg.width = 0x3;    // binary 11 (see quick Quick Reference sheet to DMA programming in the cotulla EAS)
    CmdBuff.DcmdReg.size = 0x3;     // binary 11
    CmdBuff.DcmdReg.endian = 0;     // little endian
    CmdBuff.DcmdReg.flybyt = 0;     // Flowthrough
    CmdBuff.DcmdReg.flybys = 0;     // Flowthrough
    CmdBuff.DcmdReg.endirqen = 1;   // 1 means Interrupt when decrement length = 0;
    CmdBuff.DcmdReg.startirqen = 0; // 1 means Interrupt when the desc is loaded
    if (dir == WAVEDIR_PLAYING) {
        CmdBuff.DcmdReg.flowtrg = 1;    // 1 means the target is an external peripheral
        CmdBuff.DcmdReg.flowsrc = 0;    // 1 means the source is an external peripheral (and needs flow control)
        CmdBuff.DcmdReg.inctrgadd = 0;  // 1 means increment the target address (since it's memory)
        CmdBuff.DcmdReg.incsrcadd = 1;  // 1 means increment the source address (since it's a peripheral)
    }
    else {
        CmdBuff.DcmdReg.flowtrg = 0;    // 1 means the target is an external peripheral
        CmdBuff.DcmdReg.flowsrc = 1;    // 1 means the source is an external peripheral (and needs flow control)
        CmdBuff.DcmdReg.inctrgadd = 1;  // 1 means increment the target address (since it's memory)
        CmdBuff.DcmdReg.incsrcadd = 0;  // 1 means increment the source address (since it's a peripheral)
    }

    // Fill the descriptors
    DMADescriptorChannelType *pDesc = (DMADescriptorChannelType *)
                                        (pCxt->descVirt + idx * DMA_DESC_SIZE);
     // address of the next descriptor
    pDesc->ddadr = pCxt->descPhys + NEXT_BUFFER(idx) * DMA_DESC_SIZE;
    if (dir == WAVEDIR_PLAYING) {
        // For playing
        pDesc->dtadr = I2S_AUDIO_BUFFER;  // destination address
        pDesc->dsadr = pCxt->bufPhys + idx * AUDIO_BUFSIZE; // source address

    }
    else {
        // For recording
        pDesc->dtadr = pCxt->bufPhys + idx * AUDIO_BUFSIZE;  // destination address
        pDesc->dsadr = I2S_AUDIO_BUFFER; // source address
    }
    pDesc-> dcmd = CmdBuff.DcmdDword;   // size and cmd values
}

// Populate DMAC Descriptors and fill DMA registers with descriptor pointers,
// Start DMA transmit
BOOL InitDMAC(AudioContext *pCxt, int Channel)
{
    // 1. Set the physical address of the frame descriptors
    // 2. setup the request channel map register (with channel and valid)
    // 3. Load the address of the first descriptor into the channel group. The rest of
    //    the registers are loaded when the RUN bit is set in the DCSR.

    DBGOUT(kprintf("InitDmac %d\n", Channel));
    assert(Channel == pCxt->dmaChOut || Channel == pCxt->dmaChIn);
    DDADR(Channel)= pCxt->descPhys;

    if (Channel == pCxt->dmaChOut) {
        DRCMR3 = DMA_MAP_VALID_MASK | pCxt->dmaChOut;
    }
    else if (Channel == pCxt->dmaChIn) {
        DRCMR2 =  DMA_MAP_VALID_MASK | pCxt->dmaChIn;
    }

    DCSR(Channel) |=  DCSR_RUN;      // set the RUN bit

    return TRUE;
}


// To stop DMA transmit
BOOL  StopDmac(int Channel)
{
    //TODO: Do I need to set the dma length to 0
    DCSR(Channel) &=  ~DCSR_RUN;  //clear the run but

    return TRUE;
}

void FreeBuffer(AudioContext *pCxt)
{
    DBGOUT(kprintf("FreeBuffer pCxt->nBufCnt = %d\n", pCxt->nBufCnt));
    pCxt->nBufCnt = 0;
}

ECode AllocBuffer(AudioContext *pCxt)
{
    UINT nBufCnt;
    // Dynamic calculate buffer count.
    // The maximal playing length in the driver buffer is about 1s.
    // And nBufCnt must be equal or less than AUDIO_BUFCNT.
    // No matter the user input format, the internal audio format.
    // For AC97 there is always 2 channels & 16bits.
    // 1s, 2 channels, 2 bytes per sample -> pCxt->nRate * 4
    nBufCnt = (pCxt->nRate * 4) / AUDIO_BUFSIZE;
    if (nBufCnt > AUDIO_BUFCNT) nBufCnt = AUDIO_BUFCNT;
    pCxt->nBufCnt = nBufCnt;

    DBGOUT(kprintf("AllocBuffer pCxt->nBufCnt = %d\n", pCxt->nBufCnt));

    memset((void *)pCxt->descVirt, 0, 1 << PAGE_SHIFT);
    memset((void *)pCxt->bufVirt, 0, nBufCnt << PAGE_SHIFT);
    return NOERROR;
}

// Convert size of bytes from user input (8/16 bits, 1/2 channels)
// to hardware transfer(16 bits, 2 channels)
INLINE GetFillSize(UINT nCopySize, UINT nBits, UINT nChannels)
{
    int nFillSize;

    if (nBits == 16 && nChannels == 2) nFillSize = nCopySize; // Most common
    else if (nBits == 16 && nChannels == 1) nFillSize = nCopySize << 1;
    else if (nBits == 8 && nChannels == 2) nFillSize = nCopySize << 1;
    else /*if (nBits == 8 && nChannels == 1)*/ nFillSize = nCopySize << 2;

    return nFillSize;
}

// Convert size of bytes from hardware transfer(16 bits, 2 channels)
// to user input (8/16 bits, 1/2 channels)
INLINE GetCopySize(UINT nFillSize, UINT nBits, UINT nChannels)
{
    int nCopySize;

    if (nBits == 16 && nChannels == 2) nCopySize = nFillSize; // Most common
    else if (nBits == 16 && nChannels == 1) nCopySize = nFillSize >> 1;
    else if (nBits == 8 && nChannels == 2) nCopySize = nFillSize >> 1;
    else /*if (nBits == 8 && nChannels == 1)*/ nCopySize = nFillSize >> 2;

    return nCopySize;
}


UINT FillBuffer(AudioContext *pCxt, const BYTE *pBuf, INT Size)
{
    BYTE *pBufDes;
    UINT nFillSize, nCopySize, nRemainSize, nTotalCopySize = 0;

    assert(pCxt->nBits == 8 || pCxt->nBits == 16);
    assert(pCxt->nChannels == 1 || pCxt->nChannels == 2);
    nRemainSize = Size;

    do {
        pBufDes = (BYTE *)(pCxt->bufVirt + AUDIO_BUFSIZE * pCxt->nFillingBufIdx);

        // Remain bytes in current filling buffer
        nFillSize = AUDIO_BUFSIZE;
        nCopySize = GetCopySize(nFillSize, pCxt->nBits, pCxt->nChannels);

        // Maximal copy count
        if (nCopySize > nRemainSize) {
            nCopySize = nRemainSize;
            nFillSize = GetFillSize(nCopySize, pCxt->nBits, pCxt->nChannels);
            // DMA transfer size must be an integer multiple of 32 bytes
            if (nFillSize % 32) {
                nFillSize = nFillSize & (~0x1f); // Round up 32
            }
        }

        pCxt->funcWriteCopy(pBufDes, (BYTE *)pBuf, nCopySize);
        pBuf += nCopySize;
        nTotalCopySize += nCopySize;

        FillDescriptors(pCxt, WAVEDIR_PLAYING, pCxt->nFillingBufIdx, nFillSize);

        pCxt->nFillingBufIdx = NEXT_BUFFER(pCxt->nFillingBufIdx);

        if (pCxt->nFillingBufIdx == pCxt->nTransBufIdx) {
            pCxt->status = STATUS_BUFFER_FULL;
            DBGOUT(kprintf("STATUS_BUFFER_FULL\n"));
            break;
        }

        nRemainSize -= nCopySize;
        //DBGOUT(kprintf("remain %d,nidex %d,total %d\n",nRemainSize,pCxt->nFillingBufIdx,nTotalCopySize));
    } while (nRemainSize > 0);

    return nTotalCopySize;

}

//for audio in
UINT GetBuffer(AudioContext *pCxt, BYTE *pBuf, INT Size)
{
    BYTE *pBufSrc;
    UINT nCanCopySize, nCopySize, nRemainSize, nTotalCopySize = 0;

    assert(pCxt->nBits == 8 || pCxt->nBits == 16);
    assert(pCxt->nChannels == 1 || pCxt->nChannels == 2);
    nRemainSize = Size;

    do {
        // Remain bytes in current filling buffer
        nCanCopySize = GetCopySize(AUDIO_BUFSIZE - pCxt->nFillingBufPos,
                pCxt->nBits, pCxt->nChannels);

        // Maximal copy count
        if (nCanCopySize > nRemainSize) nCopySize = nRemainSize;
        else nCopySize = nCanCopySize;

        pBufSrc = (BYTE *)(pCxt->bufVirt +
                AUDIO_BUFSIZE * pCxt->nFillingBufIdx + pCxt->nFillingBufPos);
        pCxt->funcReadCopy(pBuf, pBufSrc, nCopySize);
        pBuf += nCopySize;
        nTotalCopySize += nCopySize;

        if (nCanCopySize <= nRemainSize) {
            pCxt->nFillingBufIdx = NEXT_BUFFER(pCxt->nFillingBufIdx);
            pCxt->nFillingBufPos = 0;
        }
        else {
            pCxt->nFillingBufPos += nCopySize;
        }

        if (pCxt->nFillingBufIdx == pCxt->nTransBufIdx) {
            pCxt->status = STATUS_BUFFER_EMPTY;
            DBGOUT(kprintf("STATUS_BUFFER_EMPTY, %d\n", pCxt->nTransBufIdx));
            break;
        }

        nRemainSize -= nCopySize;
        DBGOUT(kprintf("remain %d,nidex %d,total %d\n",nRemainSize,pCxt->nFillingBufIdx,nTotalCopySize));
    } while (nRemainSize > 0);

    return nTotalCopySize;

}

// bFreeBuf == TRUE: Really close audio device and free buffers.
// bFreeBuf == FALSE: Close audio device for power management.
void Audio_Close(AudioContext *pCxt, BOOL bFreeBuf)
{
    if (pCxt->bDmaInProgress) {
        int dmaCh = (pCxt->waveDir == WAVEDIR_RECORDING) ?
            pCxt->dmaChIn : pCxt->dmaChOut;
        pCxt->bDmaInProgress = FALSE;
        StopDmac(dmaCh);
    }

    if (pCxt->waveDir == WAVEDIR_PLAYING) {
        // Close wave out device
        DBGOUT(kprintf("Close wave out device\n"));
        I2S_AftPlay();
        // Don't close codec right now in order to avoid pops and clicks
        //Codec_Close(pCxt);
    }
    else if (pCxt->waveDir == WAVEDIR_RECORDING) {
        // Close wave in device
        DBGOUT(kprintf("Close wave in device\n"));
        Codec_SelectAudioInChannel(pCxt, 0);
        I2S_AftRecord();
    }

    if (bFreeBuf) {
        FreeBuffer(pCxt);
        pCxt->waveDir = WAVEDIR_NOTPREPARED;
    }
}

void Audio_Reset(AudioContext *pCxt)
{
    int dmaCh = pCxt->dmaChOut; // Mute compiler warning

    if (pCxt->waveDir == WAVEDIR_PLAYING) {
        dmaCh = pCxt->dmaChOut;
    }
    else if (pCxt->waveDir == WAVEDIR_RECORDING) {
        dmaCh = pCxt->dmaChIn;
    }
    else return;

    if (pCxt->bDmaInProgress) {
        pCxt->bDmaInProgress = FALSE;
        StopDmac(dmaCh);
    }
    Audio_Initial(pCxt, pCxt->waveDir, 0);
}

ECode AudioIn_Read( AudioContext *pCxt,
        void *pData, UINT dataLen, UINT * pRead)
{
    if (pCxt->waveDir != WAVEDIR_RECORDING) return E_ACCESS_DENIED;

    // Deal with recording when headset is plugged in or out
    if ((WAVEINSOURCE_AUXMIC == pCxt->waveInSource) &&
        (!(pCxt->headsetStatus & HEADSET_PLUG))) {
        DBGOUT(kprintf("Can't continue recording with MIC on headset.\n"));
        return E_ACCESS_DENIED;
    }

    // If DMA transfer doens't start, just do it!
    if (!pCxt->bDmaInProgress) {
        pCxt->bDmaInProgress = TRUE;
        pCxt->status = STATUS_BUFFER_EMPTY;
        INTC_DMAC_INT_EN(ICMR);
        InitDMAC(pCxt, pCxt->dmaChIn);
    }

    s_syncAudioIn.Clear();

    if (/*pCxt->bDmaInProgress && */pCxt->status == STATUS_BUFFER_EMPTY) {
        // No buffer to read, so should wait here
        DBGOUT(kprintf("Here wait to get a filled buffer.\n"));
        // Wait here , the max time is MAX_WAIT_TIME ms
        WaitResult wr;
        s_syncAudioIn.TryWait(MAX_WAIT_TIME, &wr, NULL);

        if (WaitResult_TimedOut == wr) {
            // No buffers to use after waiting, perhaps the device isn't open.
            DBGOUT(kprintf("Time is out. pCxt->nTransBufIdx = %d, pCxt->nFillingBufIdx = %d\n",
                pCxt->nTransBufIdx, pCxt->nFillingBufIdx));
            return E_TIMED_OUT;
        }
    }

    *pRead = GetBuffer(pCxt, (BYTE *)pData, dataLen);

    return NOERROR;
}

ECode AudioOut_Write( AudioContext *pCxt,
        void *pData, UINT dataLen, UINT * pWritten)
{

    if (pCxt->waveDir != WAVEDIR_PLAYING) return E_ACCESS_DENIED;

    s_syncAudioOut.Clear();
    if (pCxt->bDmaInProgress && pCxt->status == STATUS_BUFFER_FULL) {
        WaitResult wr;
        s_syncAudioOut.TryWait(MAX_WAIT_TIME, &wr, NULL);

        if (WaitResult_TimedOut == wr) {
            // No buffers to use after waiting, perhaps the device isn't open.
            DBGOUT(kprintf("Time is out. pCxt->nTransBufIdx = %d, pCxt->nFillingBufIdx = %d\n",
                pCxt->nTransBufIdx, pCxt->nFillingBufIdx));
            return E_TIMED_OUT;
        }
    }

    if (!pCxt->bDmaInProgress) {
        pCxt->nFillingBufPos = 0; // Reserved for replay
        pCxt->nFillingBufIdx = 0;
        pCxt->nTransBufIdx = 0;

        INTC_DMAC_INT_EN(ICMR);
    }

    *pWritten = FillBuffer(pCxt, (byte*)pData, dataLen);

    // If DMA transfer doens't start, just do it!
    if (!pCxt->bDmaInProgress) {
        pCxt->bDmaInProgress = TRUE;
        InitDMAC(pCxt, pCxt->dmaChOut);
    }

    return NOERROR;
}

BOOL DmaChOutIsr(AudioContext *pCxt, PBOOL pWakeUp)
{
#if (_TEST_TYPE == 41)
    ULONG uTimerStart = OSCR0;
#endif //_TEST_TYPE == 41

    //if the out channel stopped at the end of a buffer xfer
    if (DCSR(pCxt->dmaChOut) & DCSR_ENDINTR)
    {
        DCSR(pCxt->dmaChOut) |= DCSR_ENDINTR;

        pCxt->nTransBufIdx = NEXT_BUFFER(pCxt->nTransBufIdx);

        if (pCxt->status == STATUS_BUFFER_FULL) {
            pCxt->status = STATUS_NORMAL;
            DBGOUT(kprintf("STATUS_NORMAL->pulse, next: %d\n", pCxt->nTransBufIdx));
            s_syncAudioOut.NotifyByIsr();
        }
        else if (pCxt->nTransBufIdx == pCxt->nFillingBufIdx) {
            DBGOUT(kprintf("STATUS_BUFFER_EMPTY->STOP\n"));
            StopDmac(pCxt->dmaChOut);
            pCxt->status = STATUS_BUFFER_EMPTY;
            pCxt->bDmaInProgress = FALSE;
        }
    }
    else if (DCSR(pCxt->dmaChOut) & DCSR_BUSERRINTR) {
        DBGOUT(kprintf("DMA hannel %d bus error!\n", pCxt->dmaChOut));
        DCSR(pCxt->dmaChOut) |= DCSR_BUSERRINTR;
        StopDmac(pCxt->dmaChOut);
        pCxt->status = STATUS_INITIAL;
        pCxt->bDmaInProgress = FALSE;
    }

#if (_TEST_TYPE == 41)
    ULONG uTimerEnd = OSCR0;
    if (nDmaChOutIsrCounter >= SAMPLETIMES) nDmaChOutIsrCounter = 0;
    ulDmaChOutIsrTime[nDmaChOutIsrCounter++] = \
                            (uTimerEnd >= uTimerStart) ? \
                            (uTimerEnd - uTimerStart) : \
                            ((ULONG)0xFFFFFFFF - uTimerStart + uTimerEnd);

#endif //_TEST_TYPE == 41

    return TRUE;
}

BOOL DmaChRcvIsr(AudioContext *pCxt, PBOOL pWakeUp)
{
#if (_TEST_TYPE == 41)
    ULONG uTimerStart = OSCR0;
#endif //_TEST_TYPE == 41

    //if the in channel stopped at the end of a buffer xfer
    if (DCSR(pCxt->dmaChIn) & DCSR_ENDINTR) {

        DCSR(pCxt->dmaChIn) |= DCSR_ENDINTR;
        pCxt->nTransBufIdx = NEXT_BUFFER(pCxt->nTransBufIdx);
        if (pCxt->status == STATUS_BUFFER_EMPTY) {
            pCxt->status = STATUS_NORMAL;
            DBGOUT(kprintf("STATUS_NORMAL->pulse, next: %d\n", pCxt->nTransBufIdx));
            s_syncAudioIn.NotifyByIsr();
        }
        else if (pCxt->nTransBufIdx == pCxt->nFillingBufIdx) {
            StopDmac(pCxt->dmaChIn);
            DBGOUT(kprintf("STATUS_BUFFER_FULL->STOP\n"));
            pCxt->status = STATUS_BUFFER_FULL;
            pCxt->bDmaInProgress = FALSE;
        }
    }
    else if (DCSR(pCxt->dmaChIn) & DCSR_BUSERRINTR) {
        DBGOUT(kprintf("DMA hannel %d bus error!\n", pCxt->dmaChIn));
        DCSR(pCxt->dmaChIn) |= DCSR_BUSERRINTR;
        StopDmac(pCxt->dmaChIn);
        pCxt->status = STATUS_INITIAL;
        pCxt->bDmaInProgress = FALSE;
    }

#if (_TEST_TYPE == 41)
    ULONG uTimerEnd = OSCR0;
    if (nDmaChRcvIsrCounter >= SAMPLETIMES) nDmaChRcvIsrCounter = 0;
    ulDmaChRcvIsrTime[nDmaChRcvIsrCounter++] = \
                            (uTimerEnd >= uTimerStart) ? \
                            (uTimerEnd - uTimerStart) : \
                            ((ULONG)0xFFFFFFFF - uTimerStart + uTimerEnd);
#endif //_TEST_TYPE == 41

    return TRUE;
}

ECode Audio_FlushBuffer(AudioContext *pCxt)
{
    if (pCxt->waveDir == WAVEDIR_PLAYING && pCxt->bDmaInProgress) {
        int nRemainBufCnt;

        // NOTE: The buffers make a loop
        if (pCxt->nFillingBufIdx > pCxt->nTransBufIdx) {
            nRemainBufCnt = pCxt->nFillingBufIdx - pCxt->nTransBufIdx;
        }
        else {
            nRemainBufCnt = pCxt->nFillingBufIdx + pCxt->nBufCnt - pCxt->nTransBufIdx;
        }

        // Sleep some time, until the DMA buffers are all transfered.
        // The time is a approximate value.
        // For I2S there is always 2 channels & 16bits.
        // 2 channels, 2 bytes per sample -> pCxt->nRate * 4

        DzSleep(nRemainBufCnt * AUDIO_BUFSIZE  * 100 / (pCxt->nRate * 4), NULL);
    }

    return NOERROR;
}

ECode Audio_GetState(AudioContext *pCxt, Audio_Status *pStatus)
{
    unsigned int state = 0;

    if (WAVEDIR_PLAYING == pCxt->waveDir) {
        state = STATE_AUDIOOUT;
        if (pCxt->speakerStatus & SPEAKER_OPEN) {
            state |= AUDIOOUT_SPEAKER;
        }
        if ((pCxt->headsetStatus & HEADSET_OPEN)
             && (pCxt->headsetStatus & HEADSET_PLUG) ) {
            state |= AUDIOOUT_HEADSET;
        }
    }
    else if (WAVEDIR_RECORDING == pCxt->waveDir) {
        state = STATE_AUDIOIN;
        state &= ~0x00ff0000;
        if (WAVEINSOURCE_MAINMIC == pCxt->waveInSource) {
            state |= AUDIOIN_MAINMIC << 16;
        }
        if (WAVEINSOURCE_AUXMIC == pCxt->waveInSource) {
            state |= AUDIOIN_HEADSET << 16;
        }
    }
    pStatus->State = state;

    if (WAVEDIR_PLAYING == pCxt->waveDir) {
        pStatus->AudioOutParams.SampleRate = pCxt->nRate;
        pStatus->AudioOutParams.BitsPerSample = (short)pCxt->nBits;
        pStatus->AudioOutParams.Channels = (short)pCxt->nChannels;
    }
    else if (WAVEDIR_RECORDING == pCxt->waveDir) {
        pStatus->AudioInParams.SampleRate = pCxt->nRate;
        pStatus->AudioInParams.BitsPerSample = (short)pCxt->nBits;
        pStatus->AudioInParams.Channels = (short)pCxt->nChannels;
    }

    return NOERROR;
}

ECode AudioInstall(AudioContext *pCxt)
{
#if (_TEST_TYPE == 41)      /* Test for Runtime of ISR */
    memset(ulDmaChOutIsrTime, 0, sizeof(ULONG) * SAMPLETIMES);
    memset(ulDmaChRcvIsrTime, 0, sizeof(ULONG) * SAMPLETIMES);
#endif //_TEST_TYPE == 41

    SSP_Initial();

    // GPIO Settings
    I2S_SetGpios();

    // Init audio codec
    Codec_Init(pCxt);

    // Default volume
    pCxt->volLeftOut = 0xa0;
    pCxt->volRightOut = 0xa0;

    // Select DMA channels for playing & recording
    pCxt->dmaChOut = Drv_DmaInstall(0, (void *)pCxt, (void *)DmaChOutIsr);
    pCxt->dmaChIn = Drv_DmaInstall(0, (void *)pCxt, (void *)DmaChRcvIsr);

    kprintf("DMA Channel for audio out: %d\n", pCxt->dmaChOut);
    kprintf("DMA Channel for audio in: %d\n", pCxt->dmaChIn);

    assert(pCxt->dmaChOut >= 0 && pCxt->dmaChOut < 32);
    assert(pCxt->dmaChIn >= 0 && pCxt->dmaChIn < 32);

    pCxt->waveDir = WAVEDIR_NOTPREPARED;
    pCxt->status = STATUS_INITIAL;
    pCxt->waveInSource = WAVEINSOURCE_MAINMIC;

    // Close speaker & open headset defaulty
    pCxt->speakerStatus = 0;
    pCxt->headsetStatus = HEADSET_OPEN; // headset open defaultly now

    pCxt->nBufCnt = 0; // Alloc buffer when using

    Codec_InitHeadset(pCxt);

    //
    // Init buffers
    //

    // Allocate multiple buffers for DMA transfer (maximal number)
    pCxt->bufVirt =  (Address)Drv_AllocBuffer(AUDIO_BUFCNT, DRVBUF_SPEED_FAST);
    assert(pCxt->bufVirt && "Alloc audio buffer error!");
    pCxt->bufPhys = Drv_GetBufferPhysicalAddress((void *)pCxt->bufVirt);

    // Get DMA Descriptors
    pCxt->descVirt = (Address)Drv_AllocBuffer(1, DRVBUF_SPEED_FAST);
    assert(pCxt->descVirt && "Alloc audio DMA descriptors error!");
    pCxt->descPhys = Drv_GetBufferPhysicalAddress((void *)pCxt->descVirt);

    kprintf("Initial Audio Successfully!\n");

    return 0;
}

ECode Audio_Initial(AudioContext *pCxt, WAVEDIR dir, ULONG flag)
{
    if ((flag & (AUDIO_ALLOCBUFFER | AUDIO_OPENHARDWARE))
        == (AUDIO_ALLOCBUFFER | AUDIO_OPENHARDWARE)) {
        // When initializing audio device,
        // if audio device is used now, release it!
        if (pCxt->waveDir == WAVEDIR_PLAYING){
            //if (pCxt->bDmaInProgress) Audio_FlushBuffer(pCxt);
        }
//        else if (pCxt->waveDir == WAVEDIR_RECORDING){
//        }
        Audio_Close(pCxt, TRUE); // Efficiency problem?
    }

    if (flag & AUDIO_ALLOCBUFFER) {
        AllocBuffer(pCxt);

        if (dir == WAVEDIR_RECORDING) {
            UINT i;
            assert(pCxt->nBufCnt);
            for (i = 0; i < pCxt->nBufCnt; i++) {
                FillDescriptors(pCxt, dir, i, AUDIO_BUFSIZE);
            }
        }

        // Adjust basic information
        if (pCxt->nBits != 8 && pCxt->nBits != 16) pCxt->nBits = 16;
        if (pCxt->nChannels != 1 && pCxt->nChannels != 2) pCxt->nChannels = 1;

        // Add callback functions for memory copy
        if (pCxt->nChannels == 1) {
            if (pCxt->nBits == 16) {
                pCxt->funcWriteCopy = memcpy_m16_s16;
                pCxt->funcReadCopy = memcpy_s16_m16;
            }
            else {
                pCxt->funcWriteCopy = memcpy_m8_s16;
                pCxt->funcReadCopy = memcpy_s16_m8;
            }
        }
        else {
            if (pCxt->nBits == 16) {
                pCxt->funcWriteCopy = memcpy_s16_s16;
                pCxt->funcReadCopy = memcpy_s16_s16;
            }
            else {
                pCxt->funcWriteCopy = memcpy_s8_s16;
                pCxt->funcReadCopy = memcpy_s16_s8;
            }
        }
    }

    if (flag & AUDIO_OPENHARDWARE) {
        if (dir == WAVEDIR_PLAYING) {
            I2S_PrePlay();
            Codec_SetSampleRate(pCxt->nRate, dir);
            Codec_Open(pCxt, dir);

//            pCxt->speakerStatus |= SPEAKER_NEED_INITIAL;
//            pCxt->headsetStatus |= HEADSET_NEED_INITIAL;
            Codec_SetAudioOutVolume(pCxt,
                    pCxt->volLeftOut, pCxt->volRightOut);

            if (pCxt->speakerStatus & SPEAKER_OPEN) {
                Codec_OpenAmplifier(TRUE);
            }

        }
        else if (dir == WAVEDIR_RECORDING) {
            I2S_PreRecord();
            Codec_SetSampleRate(pCxt->nRate, dir);
            Codec_Open(pCxt, dir);

            // Audio input source
            if (pCxt->waveInSource == WAVEINSOURCE_MAINMIC) {
                // Mic on the phone
                Codec_SelectAudioInChannel(pCxt, AUDIO_USE_MAINMIC);
            }
            else if (pCxt->waveInSource == WAVEINSOURCE_AUXMIC) {
                // Mic on the headset
                Codec_SelectAudioInChannel(pCxt, AUDIO_USE_AUXMIC);
            }
        }
        //pCxt->headsetStatus &= ~HEADSET_PLUG_ISR; // Clear this flag if it set
    }

    pCxt->nFillingBufPos = 0;
    pCxt->nFillingBufIdx = 0;
    pCxt->nTransBufIdx = 0;

    pCxt->waveDir = dir;
    pCxt->status = STATUS_NORMAL;

//    pCxt->status = STATUS_BUFFER_EMPTY;

    DBGOUT(kprintf(
        "Audio_Initial Debug: dir = %d, flag = 0x%x, pCxt->speakerStatus = 0x%x\n",
        dir, flag, pCxt->speakerStatus));

    DBGOUT(kprintf("%s:Rate=%d, BitsPerSample=%d, Channels=%d\n",
            dir == WAVEDIR_PLAYING ? L"Playing" : L"Recording",
            pCxt->nRate, pCxt->nBits, pCxt->nChannels));

    return NOERROR;
}


#if (_TEST_TYPE == 41)      /* Test for Runtime of ISR */
void SetOutIsrTime(/* [out] */ EzByteBuf * pOutData)
{
    memcpy(pOutData->GetPayload(),
                ulDmaChOutIsrTime,
                sizeof(ULONG) * SAMPLETIMES);
    pOutData->SetUsed(SAMPLETIMES * sizeof(ULONG));
}
void SetRcvIsrTime(/* [out] */ EzByteBuf * pOutData)
{
    memcpy(pOutData->GetPayload(),
                ulDmaChRcvIsrTime,
                sizeof(ULONG) * SAMPLETIMES);
    pOutData->SetUsed(SAMPLETIMES * sizeof(ULONG));
}
#endif //_TEST_TYPE == 41


ECode Audio_Create(AudioContext *pCxt)
{
    return AudioInstall(pCxt);
}
